#include "analysis.hpp"
#include "instructions.hpp"

#include <cstdio>
#include <iostream>
using namespace std;

namespace analysis
{


Pass::Pass(ostream& _out)
    : out(_out), has_errors(false)
{ }


void Pass::emit_warning(const ast::Node& node, const char* msg)
{
  out << node.location.filename
      << ':' << node.location.first_line-LINE_OFFSET
      << ": warning: " << msg
      << endl;
}


void Pass::emit_error(const ast::Node& node, const char* msg)
{
  out << node.location.filename
      << ':' << node.location.first_line-LINE_OFFSET
      << ": error: " << msg
      << endl;

  has_errors = true;
}


bool Pass::succeeded()
{
  return !has_errors;
}


SemanticCheckPass::SemanticCheckPass(ostream& _out)
    : Pass(_out)
{ }


void SemanticCheckPass::visit(Program& program)
{
  if (program.dataSections->empty() && program.textSections->empty()) {
    emit_error(program, "empty assembly file");
  }
}


void SemanticCheckPass::visit(Directive& d)
{
  switch (d.dirType) {
    case Tstart:
    case Tglobal:
    case Textern:
      if (!d.labels->empty()) {
        emit_warning(d, "label points to a directive, not a declaration");
      }
      break;

    default:
      //if (d.labels->empty()) {
      //  emit_warning(d, "no label defined for this declaration");
      //}
      break;
  }

  switch (d.dirType) {
    case Tresw:
    case Tresb:
      if (d.arg.size <= 0) {
        emit_error(d, "number of elements defined in declaration isn't positive");
      }
      break;

    default:
      break;
  }
}


void SemanticCheckPass::visit(Instruction& instr)
{
  int opcode = instr.opcode;
  int format = instr.operand_format;

  Operand& op2 = *instr.op2;
  Operand& op3 = *instr.op3;

  if (format == 3) {
    if (!op3.isLabel()) {
      op3.type = Operand::ConstantL;
    }

    if (op2.type != Operand::ZeroRegister) {
      if (op2.type != Operand::Register || op2.arg.value != 0) {
        emit_error(instr, "malformed instruction with format type 3");
      }

      op2.type = Operand::ZeroRegister;
    }
  }

  if ((risco::is_ldp(opcode) || risco::is_stp(opcode)) && format != 1) {
    emit_error(instr, "ldp* or stp* instructions must have format type 1");
  }
}


void SemanticCheckPass::visit(Operand& op)
{
  char msg[500];

  switch (op.type) {
    case Operand::Register:
      if (op.arg.value < 0 || op.arg.value > 31)
        emit_error(op, "register id out of range");

      break;

    case Operand::ZeroRegister:
      if (op.arg.value != 0)
        emit_error(op, "register R00 has wrong id");

      break;

    case Operand::ConstantP:
      if (op.arg.value < -1024 || op.arg.value > 1023) {
        sprintf(msg, "constant value outside Kp range %d", op.arg.value);
        emit_warning(op, msg);
      }

      break;

    case Operand::ConstantL:
      if (op.arg.value < -65536 || op.arg.value > 65535)
        emit_warning(op, "constant value outside Kgl range");

      break;

    default:
      break;
  }
}


SymbolInfo::SymbolInfo()
    : is_global(false),
      is_extern(false),
      is_code(false),
      offset(-1)
{ }


SymbolInfo::SymbolInfo(int _offset)
    : is_global(false),
      is_extern(false),
      is_code(false),
      offset(_offset)
{ }


bool SymbolInfo::is_defined() const
{
  return offset != -1;
}


bool SymbolTable::contains(const Label& label) const
{
  return find(label) != end();
}


SymbolTableBuildPass::SymbolTableBuildPass(ostream& _out)
    : Pass(_out),
      startLabel(new string("_main")),
      textOffset(0),
      dataOffset(0),
      currentNode(NULL)
{ }


void SymbolTableBuildPass::visit(Program& program)
{
  currentNode = &program;
  normalize_label(*startLabel);
}


void SymbolTableBuildPass::visit(DataSection& sec)
{
  topLabel.clear();
  currentNode = &sec;
}


void SymbolTableBuildPass::visit(TextSection& sec)
{
  topLabel.clear();
  currentNode = &sec;
}


void SymbolTableBuildPass::visit(Directive& d)
{
  currentNode = &d;
  process_labels(*d.labels, dataOffset, false);

  Label* label = d.arg.str;

  switch (d.dirType) {
    case Tascii:
      dataOffset += d.arg.str->length();
      break;

    case Tasciiz:
      dataOffset += d.arg.str->length()+1;
      break;

    case Tresw:
      dataOffset += d.arg.size * 4;
      break;

    case Tresb:
      dataOffset += d.arg.size;
      break;

    case Tword:
      if (d.argType == Directive::IntList) {
        dataOffset += d.arg.intList->size() * 4;
      }
      else if (d.argType == Directive::Label) {
        dataOffset += 4;
        normalize_label(*d.arg.str);
      }
      break;

    case Tbyte:
      dataOffset += d.arg.intList->size();
      break;

    case Tstart:
      normalize_label(*label);
      startLabel = label;

      break;

    case Tglobal:
      normalize_label(*label);

      if (symbol_table.contains(*label)) {
        SymbolInfo& symbol = symbol_table[*label];

        if (symbol.is_extern) {
          emit_error(*currentNode, "extern symbol can't be global");
        }

        if (symbol.is_global) {
          emit_error(*currentNode, "multiple .global directives for symbol");
        }

        symbol.is_global = true;
      }
      else {
        SymbolInfo symbol;
        symbol.is_global = true;
        symbol_table[*label] = symbol;
      }

      break;

    case Textern:
      normalize_label(*label);

      if (symbol_table.contains(*label)) {
        SymbolInfo& symbol = symbol_table[*label];

        if (symbol.is_global) {
          emit_error(*currentNode, "global symbol can't be extern");
        }

        if (symbol.is_extern) {
          emit_error(*currentNode, "multiple .extern directives for symbol");
        }

        symbol.is_extern = true;
      }
      else {
        SymbolInfo symbol;
        symbol.is_extern = true;
        symbol_table[*label] = symbol;
      }

    default:
      break;
  }
}


void SymbolTableBuildPass::visit(Instruction& instr)
{
  currentNode = &instr;

  process_labels(*instr.labels, textOffset, true);

  textOffset += 4;

  Operand& op3 = *instr.op3;
  int format = instr.operand_format;

  if (format != 1 && op3.isLabel()) {
    Label& label = *op3.arg.str;
    normalize_label(label);
  }
}


void SymbolTableBuildPass::normalize_label(Label& label)
{
  label += '#';
  label += currentNode->location.filename;

  if (label[0] == '.') {
    if (topLabel.empty()) {
      emit_error(*currentNode, "local label used outside scope");
    }
    else {
      label = topLabel + label;
    }
  }
}


void SymbolTableBuildPass::process_labels(LabelList& labels, uint offset, bool is_code)
{
  for (uint i = 0; i < labels.size(); ++i) {
    Label& label = *labels[i];

    if (label[0] != '.') {
      topLabel = label;
    }
    else if (topLabel.empty()) {
      emit_error(*currentNode, "local label defined without scope");
      continue;
    }
    else {
      label = topLabel + label;
    }

    normalize_label(label);

    SymbolInfo* symbol;

    if (symbol_table.contains(label)) {
      symbol = &symbol_table[label];

      if (symbol->is_defined()) {
        emit_error(*currentNode, "label has multiple definitions");
      }

      if (symbol->is_extern) {
        emit_error(*currentNode, "extern label can't have a local definition");
      }
    }
    else {
      symbol = &symbol_table[label];
    }

    symbol->offset = offset;
    symbol->is_code = is_code;
  }
}


SymbolResolverPass::SymbolResolverPass(
    ostream& out, SymbolTable& sym_t, int dataOffset, int textOffset, Label& start)
    : Pass(out),
      symbol_table(sym_t),
      startLabel(start)
{
  symbol_table.dataSize = dataOffset;
  symbol_table.textSize = textOffset;
}


void SymbolResolverPass::visit(Program& program)
{
  currentNode = &program;
  resolve_global_symbols();
}


void SymbolResolverPass::resolve_global_symbols()
{
  char msg[500];
  SymbolTable::iterator it, it_b;

  for (it = symbol_table.begin(); it != symbol_table.end(); ++it) {
    const Label& label = it->first;
    SymbolInfo& symbol = it->second;

    if (!symbol.is_code && symbol.is_defined()) {
      symbol.offset += symbol_table.textSize;
    }

    if (symbol.is_global && !symbol.is_defined()) {
      sprintf(msg, "linker: global symbol '%s' isn't defined",
              demangle_label(label).c_str());
      emit_error(*currentNode, msg);
    }

    if (symbol.is_extern) {
      bool found = false;

      for (it_b = symbol_table.begin(); it_b != symbol_table.end(); ++it_b) {
        const Label& label_b = it_b->first;
        SymbolInfo& symbol_b = it_b->second;

        if (symbol_b.is_global && equal_labels(label, label_b)) {
          if (found) {
            sprintf(msg, "linker: multiple definitions for global symbol '%s'",
                    demangle_label(label).c_str());
            emit_error(*currentNode, msg);
          }
          else {
            globals[label] = label_b;
            found = true;
          }
        }
      }

      if (!found) {
        sprintf(msg, "linker: undefined symbol: '%s'", demangle_label(label).c_str());
        emit_error(*currentNode, msg);
      }
    }
  }
}


int SymbolResolverPass::startOffset()
{
  Label label = startLabel;

  if (symbol_table.find(label) == symbol_table.end()) {
    emit_error(*currentNode, "undefined start label");
    return -1;
  }
  else {
    return symbol_table[label].offset;
  }
}


void SymbolResolverPass::visit(Directive& directive)
{
  if (directive.dirType != Tword || directive.argType != Directive::Label) {
    return;
  }

  Label label = *directive.arg.str;

  if (globals.find(label) != globals.end()) {
    label = globals[label];
  }
  else if (symbol_table.find(label) == symbol_table.end()) {
    emit_error(directive, "undefined local label");
    return;
  }

  int value = symbol_table[label].offset;

  directive.arg.intList = new IntegerList();
  directive.arg.intList->push_back(value);
  directive.argType = Directive::IntList;
}


void SymbolResolverPass::visit(Instruction& instr)
{
  Operand& op3 = *instr.op3;
  int format = instr.operand_format;

  if (format != 1 && op3.isLabel()) {
    Label label = *op3.arg.str;

    if (globals.find(label) != globals.end()) {
      label = globals[label];
    }
    else if (symbol_table.find(label) == symbol_table.end()) {
      emit_error(instr, "undefined local label");
      return;
    }

    op3.arg.value = symbol_table[label].offset;

    switch (format) {
      case 2:
        op3.type = Operand::ConstantP; break;

      case 4:
        op3.type = Operand::ConstantH; break;

      case 3:
      case 5:
        op3.type = Operand::ConstantL; break;

      default:
        emit_error(instr, "mal-formed type 1 instruction");
        break;
    }
  }
}


bool SymbolResolverPass::equal_labels(const Label& l1, const Label& l2)
{
  return demangle_label(l1) == demangle_label(l2);
}


Label SymbolResolverPass::demangle_label(const Label& label)
{
  int pos = label.find('#');
  return label.substr(0, pos);
}


}
